In mathematics higher order functions are ones which take function/s as arguments and return a function as a result. Such capabilites are implemented in Python by using decorators.
Of course, you can implement a similar functionality using combination of def and lambda. However, it is generally unsafe practice to use lambda methods. In fact, the creator of Python language, Guido Van Rossum suggested its removal but the whole community of programmers was too used to it, protested against its removal and hence it remained.
A decorator is a function which takes as input another function and extends its behaviour/capability without making any explicit changes to it.
First of all you need to understand the idea of first class objects.
A first class object is an language entity that can be treated as a native variable. That means it can be created, destroyed, passed as an argument to a function, printed as you wish, etc.
In [ ]:
def func2(func1):
return func1 + 1
def func3(func2, arg):# Here func2 is being passed as a parameter.
return func2(arg)
print(func2(2))
print(func3(func2, 3))
In [ ]:
def user_defined_decorator(function1):
def wrapper():
print("This statement is being printed before the passed function is called.")
function1()
print("This statement is being printed after the passed function is called.")
return wrapper
@us
def task():
print("Lite")
user_defined_decorator(task)()
@staticmethod acts as a wrapper and informs the interpreter that the method is one which does not depend on the class or the object. It is just a method which is logical to include in the class body.
@classmethod acts as a wrapper and informs the interpreter that the method is one which depends on the class. This can be clearly understood cause the first argument is interpreted as the class type. It is a method that is commonly shared by all objects of the class type.
In [ ]:
class BITSian():
def __init__(self, name, bitsian=True):
self.name = name
self.bitsian = bitsian
@staticmethod
def is_object():
return True
def is_human(cls):
print(cls)
return True
def get_name(self):
print(self)
return self.name
def is_bitsian(self):
return str(self.name + " is a BITSian : " + str(self.bitsian))
p = BITSian("Reuben D'Souza")
print(p.get_name())
print(p.is_bitsian())
# If @staticmethod wasn't there, then this would result in an error cause arguments don't match.
print(p.is_object())
# If @classmethod wasn't there, then this "p" would be interpreted as "object" type and not "class" type.
print(classmethod(p.is_human)())
In [ ]:
class BITSian():
def __init__(self, name, bitsian=True):
self.k = name
self.bitsian = bitsian
@staticmethod
def is_object():
return True
@classmethod
def is_human(cls):
return True
@property
def name(self):
print("TEST getter")
return self.k
@name.setter
def name(self, name):
print("TEST setter")
self.k = name
def is_bitsian(self):
return str(self.name + " is a BITSian : " + str(self.bitsian))
def __str__(self):
return "BITSian : " + self.name
p = BITSian("Keerthana")
print(p)
p.name ="Rohan Prabhu"
print(p)
@slow, @XFAIL, etc are decorators used in unit testing(i.e. pytest). They will make sense only when unit testing is taught.
In [ ]:
class A():
def save(self):
print("Save in A")
class B():
def save(self):
print("Save in B")
class C(A,B):
def __init__(self):
pass
a = A()
c = C()
b = B()
In [ ]:
class A:
def test(self):
print("Test of A called")
class B(A):
def test(self):
print("Test of B called")
class C(A):
def test(self):
print("Test of C called")
class D(C, B):
pass
print(D.mro())
#d = D()
#d.test()
D.mro()[2].test(d)# This is a terrible thing to write in development level code!! Think about re-implementation.
If you write properly structured code then you should never run into the diamond paradox. If there are workarounds allowing you to override the Method Resolution Order and access the superclass B method then don't do it. Not advisable at all.
Instead think about how to restructure your code.